package com.wwp.myapplication;

import android.animation.Animator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import android.widget.Toast;

import java.util.ArrayList;

public class SceneView extends View {
    private static final String TAG = "SceneView";
    private Bitmap bitmapMain = BitmapFactory.decodeResource(getResources(), R.mipmap.bg);
    private ArrayList<Path> paths = null;
    private Paint paintPath;

    private Matrix matrix = new Matrix();
    private float[] m = new float[9];

    private boolean isCanDrag;
    private int mTouchSlop;
    private VelocityTracker mVelocityTracker;
    private FlingRunnable mFlingRunnable;

    private int downX, downY;
    private int lastX, lastY;
    private boolean pointer;

    //点击
    boolean isOnClick;
    private OnSceneClickListener onSceneClickListener;

    //缩放
    boolean isScaling;
    private float scaleInit = 0;

    public void setBitmapMain(Bitmap bitmapMain) {
        this.bitmapMain = bitmapMain;
        getInitScale();
        invalidate();
    }

    public void setPaths(ArrayList<Path> paths) {
        this.paths = paths;
        invalidate();
    }

    private void getInitScale() {
        if (bitmapMain != null) {
            if (bitmapMain.getWidth() >= getWidth() && bitmapMain.getHeight() >= bitmapMain.getHeight()) {
                scaleInit = 1;
                matrix.postScale(scaleInit, scaleInit);
            } else {
                //Math.ceil
                float xScale = (float) getWidth() / (float) bitmapMain.getWidth();
                float yScale = (float) getHeight() / (float) bitmapMain.getHeight();
                scaleInit = Math.max(xScale, yScale);
                matrix.postScale(scaleInit, scaleInit);
            }

            float currentSeatBitmapHeight = bitmapMain.getHeight() * getMatrixScaleY();
            float currentSeatBitmapWidth = bitmapMain.getWidth() * getMatrixScaleX();
            float startX = -(currentSeatBitmapWidth - getWidth()) / 2;
            float startY = -(currentSeatBitmapHeight - getHeight()) / 2;
            matrix.postTranslate(startX, startY);
        }
    }

    private float getScaleInit(Bitmap bitmap) {
        return getScaleInit(bitmap.getWidth(), bitmap.getHeight());
    }

    private float getScaleInit(int width, int height) {
        try {
            float xScale = (float) getWidth() / (float) width;
            float yScale = (float) getHeight() / (float) height;
            return Math.min(xScale, yScale);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 1;
    }

    public SceneView(Context context) {
        super(context);
    }

    public SceneView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public SceneView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        paintPath = new Paint();
        paintPath.setAntiAlias(true);
        paintPath.setColor(Color.parseColor("#ff0000"));
        paintPath.setStrokeWidth(4.0f);
        paintPath.setStyle(Paint.Style.STROKE);

        //wwp path test
        //getInitScale();

        Path p = new Path();
        p.moveTo(372*bitmapMain.getWidth()/1500, 253*bitmapMain.getHeight()/2001);
        p.lineTo(623*bitmapMain.getWidth()/1500, 253*bitmapMain.getHeight()/2001);
        p.lineTo(623*bitmapMain.getWidth()/1500, 639*bitmapMain.getHeight()/2001);
        p.lineTo(372*bitmapMain.getWidth()/1500, 639*bitmapMain.getHeight()/2001);
        p.close();
        paths = new ArrayList<>();
        paths.add(p);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (bitmapMain == null) {
            return;
        }

        if (getWidth() == 0 || getHeight() == 0)
            return;

        if (scaleInit == 0) {
            getInitScale();
            invalidate();
            return;
        }

        Bitmap bitmap = Bitmap.createBitmap(bitmapMain.getWidth(), bitmapMain.getHeight(), Bitmap.Config.ARGB_8888);
        drawBimap(new Canvas(bitmap));
        canvas.drawBitmap(bitmap, matrix, null);
    }

    private void drawBimap(Canvas canvas) {
        canvas.drawBitmap(bitmapMain, 0, 0, null);
        for (Path path : paths) {
            canvas.drawPath(path, paintPath);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        int x = (int) event.getX();
        super.onTouchEvent(event);

        scaleGestureDetector.onTouchEvent(event);
        gestureDetector.onTouchEvent(event);
        int pointerCount = event.getPointerCount();
        if (pointerCount > 1) {
            pointer = true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: ACTION_DOWN");
                mVelocityTracker = VelocityTracker.obtain();
                if (mVelocityTracker != null) {
                    mVelocityTracker.addMovement(event);
                }

                if (mFlingRunnable != null) {
                    isCanDrag = false;
                    mFlingRunnable.cancelFling();
                    mFlingRunnable = null;
                }

                pointer = false;
                downX = x;
                downY = y;
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onTouchEvent: ACTION_MOVE");
                int dx = x - lastX;
                int dy = y - lastY;

                mVelocityTracker.addMovement(event);
                mVelocityTracker.computeCurrentVelocity(1000);
                if (!isCanDrag) {
                    isCanDrag = isMoveAction(dx, dy);
                }

                if (!isScaling && !isOnClick) {
                    int downDX = Math.abs(x - downX);
                    int downDY = Math.abs(y - downY);
                    if ((downDX > 10 || downDY > 10) && !pointer) {
                        moveToEdge(dx, dy);     //wwp edge 边界计算，到边缘时，会导致晃动，原因是最小移动距离
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isCanDrag) {
                    final float vX = mVelocityTracker.getXVelocity();
                    final float vY = mVelocityTracker.getYVelocity();
                    mFlingRunnable = new FlingRunnable(getContext());
                    mFlingRunnable.fling(getWidth(), getHeight(), (int) -vX, (int) -vY);
                    post(mFlingRunnable);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
        }
        isOnClick = false;
        lastY = y;
        lastX = x;

        return true;
    }

    //勾股定理，判断斜边是否大于可滑动的一个临界值
    private boolean isMoveAction(float dx, float dy) {
        return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
    }

    //前提是图片必须大于背景
    private void moveToEdge(float dx, float dy) {
        Log.d(TAG, "moveToEdge: getTranslateX():" + getTranslateX() + ", dx:" + dx);
        //向右滑动
        if (dx > 0) {
            if (getTranslateX() <= 0) {
                if (getTranslateX() + dx > 0) {
                    dx = -getMatrixScaleX();
                }
            }
        } else {   //向左滑动
            float currentSeatBitmapWidth = bitmapMain.getWidth() * getMatrixScaleX();
            if (getTranslateX() < 0) {
                if (-dx - getTranslateX() + getWidth() > currentSeatBitmapWidth) {
                    dx = -currentSeatBitmapWidth - getTranslateX() + getWidth();
                }
            }
        }

        //向下滑动
        if (dy > 0) {
            if (getTranslateY() <= 0) {
                if (getTranslateY() + dy > 0) {
                    dy = -getMatrixScaleY();
                }
            }
        } else {   //向上滑动
            float currentSeatBitmapHeight = bitmapMain.getHeight() * getMatrixScaleY();
            if (getTranslateY() < 0) {
                if (-dy - getTranslateY() + getHeight() > currentSeatBitmapHeight) {
                    dy = -currentSeatBitmapHeight - getTranslateY() + getHeight();
                }
            }
        }
        matrix.postTranslate(dx, dy);
    }

    private boolean canMove() {
        float currentSeatBitmapWidth = bitmapMain.getWidth() * getMatrixScaleX();
        if (getTranslateX() < (getWidth() - currentSeatBitmapWidth)) return false;
        float currentSeatBitmapHeight = bitmapMain.getHeight() * getMatrixScaleY();
        if (getTranslateY() < (getHeight() - currentSeatBitmapHeight)) return false;
        if (getTranslateX() > 0 || getTranslateY() > 0) return false;
        return true;
    }

    /**
     * 自动回弹
     * 整个大小不超过控件大小的时候:
     * 往左边滑动,自动回弹到行号右边
     * 往右边滑动,自动回弹到右边
     * 往上,下滑动,自动回弹到顶部
     * <p>
     * 整个大小超过控件大小的时候:
     * 往左侧滑动,回弹到最右边,往右侧滑回弹到最左边
     * 往上滑动,回弹到底部,往下滑动回弹到顶部
     */
    private void autoScroll() {
        if (bitmapMain == null) return;

        float currentSeatBitmapWidth = bitmapMain.getWidth() * getMatrixScaleX();
        float currentSeatBitmapHeight = bitmapMain.getHeight() * getMatrixScaleY();
        float moveYLength = 0;
        float moveXLength = 0;

        //处理左右滑动的情况
        if (currentSeatBitmapWidth < getWidth()) {
            if (getTranslateX() < 0) {
                moveXLength = -getTranslateX();
            } else if (getTranslateX() + currentSeatBitmapWidth > getWidth()) {
                moveXLength = -(getTranslateX() + currentSeatBitmapWidth - getWidth());
            }
        } else {
            if (getTranslateX() < 0) {
                if (getTranslateX() + currentSeatBitmapWidth < getWidth()) {
                    moveXLength = -(getTranslateX() + currentSeatBitmapWidth - getWidth());
                }
            } else {
                moveXLength = -getTranslateX();
            }
        }

        //处理上下滑动
        if (currentSeatBitmapHeight < getHeight()) {
            if (getTranslateY() < 0) {
                moveYLength = -getTranslateY();
            } else if (getTranslateY() + currentSeatBitmapHeight > getHeight()) {
                moveYLength = -(getTranslateY() + currentSeatBitmapHeight - getHeight());
            }
        } else {
            if (getTranslateY() < 0) {
                if (getTranslateY() + currentSeatBitmapHeight < getHeight()) {
                    moveYLength = -(getTranslateY() + currentSeatBitmapHeight - getHeight());
                }
            } else {
                moveYLength = -getTranslateY();
            }
        }

        Point start = new Point();
        start.x = (int) getTranslateX();
        start.y = (int) getTranslateY();
        Point end = new Point();
        end.x = (int) (start.x + moveXLength);
        end.y = (int) (start.y + moveYLength);
        moveAnimate(start, end);
    }

    private void autoScale() {
        if (getMatrixScaleX() > (float) (scaleInit * 2)) {
            zoomAnimate(getMatrixScaleX(), (float) (scaleInit * 2));
        } else if (getMatrixScaleX() < (float) (scaleInit * 0.8)) {
            zoomAnimate(getMatrixScaleX(), (float) (scaleInit * 0.8));
        }
    }

    private float getTranslateX() {
        matrix.getValues(m);
        return m[2];
    }

    private float getTranslateY() {
        matrix.getValues(m);
        return m[5];
    }

    private float getMatrixScaleY() {
        matrix.getValues(m);
        return m[4];
    }

    private float getMatrixScaleX() {
        matrix.getValues(m);
        return m[Matrix.MSCALE_X];
    }

    private float dip2Px(float value) {
        return getResources().getDisplayMetrics().density * value;
    }

    private void moveAnimate(Point start, Point end) {
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new MoveEvaluator(), start, end);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        MoveAnimation moveAnimation = new MoveAnimation();
        valueAnimator.addUpdateListener(moveAnimation);
        valueAnimator.setDuration(400);
        valueAnimator.start();
    }

    private void zoomAnimate(float cur, float tar) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(cur, tar);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        ZoomAnimation zoomAnim = new ZoomAnimation();
        valueAnimator.addUpdateListener(zoomAnim);
        valueAnimator.addListener(zoomAnim);
        valueAnimator.setDuration(400);
        valueAnimator.start();
    }

    private void zoom(float zoom) {
        float z = zoom / getMatrixScaleX();
        matrix.postScale(z, z);
        invalidate();
    }

    private void move(PointF p) {
        float x = p.x - getTranslateX();
        float y = p.y - getTranslateY();
        matrix.postTranslate(x, y);
        invalidate();
    }

    private void move(Point p) {
        float x = p.x - getTranslateX();
        float y = p.y - getTranslateY();
        matrix.postTranslate(x, y);
        invalidate();
    }

    class MoveAnimation implements ValueAnimator.AnimatorUpdateListener {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Point p = (Point) animation.getAnimatedValue();

            move(p);
        }
    }

    class MoveEvaluator implements TypeEvaluator {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            Point startPoint = (Point) startValue;
            Point endPoint = (Point) endValue;
            int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
            int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
            return new Point(x, y);
        }
    }

    class ZoomAnimation implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float zoom = (Float) animation.getAnimatedValue();
            zoom(zoom);
        }

        @Override
        public void onAnimationCancel(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }

        @Override
        public void onAnimationStart(Animator animation) {
        }
    }

    ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            isScaling = true;
            float scaleFactor = detector.getScaleFactor();  //新的缩放比例
            if (getMatrixScaleY() * scaleFactor > scaleInit * 2) {
                scaleFactor = scaleInit * 2 / getMatrixScaleY();
            }

            if (getMatrixScaleY() * scaleFactor < scaleInit) {
                scaleFactor = scaleInit / getMatrixScaleY();
            }

            matrix.postScale(scaleFactor, scaleFactor);

            if (scaleFactor < 1) {   //缩小
                float dx = 0;
                float dy = 0;
                if (getWidth() - getTranslateX() > bitmapMain.getWidth() * getMatrixScaleX()) {
                    dx = getWidth() - (bitmapMain.getWidth() * getMatrixScaleX() + getTranslateX());
                }
                if (getHeight() - getTranslateY() > bitmapMain.getHeight() * getMatrixScaleY()) {
                    dy = getHeight() - (bitmapMain.getHeight() * getMatrixScaleY() + getTranslateY());
                }
                matrix.postTranslate(dx, dy);
            }

            invalidate();
            return true;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            isScaling = false;
        }
    });

    //单击事件拦截
    GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            Log.d(TAG, "onSingleTapConfirmed: onSingleTapConfirmed");
            if (isCanDrag) {
                isCanDrag = false;
                return super.onSingleTapConfirmed(e);
            }

            isOnClick = true;
            int x = (int) e.getX();
            int y = (int) e.getY();

            float zoom = getMatrixScaleX();
            float translateX = getTranslateX();
            float translateY = getTranslateY();
            float scaleX = zoom;
            float scaleY = zoom;

            int curX = (int) ((x - translateX) / scaleX);
            int curY = (int) ((y - translateY) / scaleY);

            for (Path path : paths) {
                boolean in = inPath(curX, curY, path);
                Toast.makeText(getContext(), "in paht " + in, Toast.LENGTH_SHORT).show();
            }
            return super.onSingleTapConfirmed(e);
        }
    });

    private boolean inPath(int x, int y, Path path) {
        RectF rectF = new RectF();
        path.computeBounds(rectF, true);
        Region re = new Region();
        re.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
        return re.contains(x, y);
    }

    private class FlingRunnable implements Runnable {
        private Scroller mScroller;
        private int mCurrentX, mCurrentY;

        public FlingRunnable(Context context) {
            mScroller = new Scroller(context);
        }

        public boolean isFling() {
            if (mScroller.computeScrollOffset()) {
                return true;
            }
            return false;
        }

        public void cancelFling() {
            mScroller.forceFinished(true);
        }

        public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
            final int minX, maxX, minY, maxY;

            //startX为当前图片左边界的x坐标
            final int startX = -(int) getTranslateX();
            float bitmapWidth = bitmapMain.getWidth() * getMatrixScaleX();
            if (bitmapWidth > viewWidth) {
                minX = 0;
                maxX = Math.round(bitmapWidth - viewWidth);
            } else {
                minX = maxX = startX;
            }

            //如果图片高度大于控件高度，同理
            final int startY = -(int) getTranslateY();
            float bitmapHeight = bitmapMain.getHeight() * getMatrixScaleY();
            if (bitmapHeight > viewHeight) {
                minY = 0;
                maxY = Math.round(bitmapHeight - viewHeight);
            } else {
                minY = maxY = startY;
            }

            mCurrentX = startX;
            mCurrentY = startY;
            if (startX != maxX || startY != maxY) {
                mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
            }
        }

        @Override
        public void run() {
            if (mScroller.isFinished()) {
                return;
            }

            if (mScroller.computeScrollOffset()) {
                final int newX = mScroller.getCurrX();
                final int newY = mScroller.getCurrY();
                matrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
                invalidate();

                mCurrentX = newX;
                mCurrentY = newY;
                postDelayed(this, 10);
            }
        }
    }

    public void setOnSceneClickListener(OnSceneClickListener onSceneClickListener) {
        this.onSceneClickListener = onSceneClickListener;
    }

    public interface OnSceneClickListener {
        void onClick(int index);
    }
}
